<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<!--
 Licensed to the Apache Software Foundation (ASF) under one or more
 contributor license agreements.  See the NOTICE file distributed with
 this work for additional information regarding copyright ownership.
 The ASF licenses this file to You under the Apache License, Version 2.0
 (the "License"); you may not use this file except in compliance with
 the License.  You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
-->

<html><head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Storm UI</title>
<link href="/css/bootstrap-3.3.1.min.css" rel="stylesheet" type="text/css">
<link href="/css/jquery.dataTables.1.10.4.min.css" rel="stylesheet" type="text/css">
<link href="/css/dataTables.bootstrap.css" rel="stylesheet" type="text/css">
<link href="/css/jsonFormatter.min.css" rel="stylesheet" type="text/css">
<link href="/css/style.css?_ts=${packageTimestamp}" rel="stylesheet" type="text/css">
<script src="/js/jquery-1.11.1.min.js" type="text/javascript"></script>
<script src="/js/jquery.dataTables.1.10.4.min.js" type="text/javascript"></script>
<script src="/js/jquery.cookies.2.2.0.min.js" type="text/javascript"></script>
<script src="/js/jquery.mustache.js" type="text/javascript"></script>
<script src="/js/url.min.js" type="text/javascript"></script>
<script src="/js/bootstrap-3.3.1.min.js" type="text/javascript"></script>
<script src="/js/jquery.blockUI.min.js" type="text/javascript"></script>
<script src="/js/jsonFormatter.min.js" type="text/javascript"></script>
<script src="/js/script.js?_ts=${packageTimestamp}" type="text/javascript"></script>
<script src="/js/visualization.js" type="text/javascript"></script>
<script src="/js/arbor.js" type="text/javascript"></script>
<script src="/js/arbor-graphics.js" type="text/javascript"></script>
<script src="/js/dataTables.bootstrap.min.js" type="text/javascript"></script>
</head>
<body>
<div class="container-fluid">
  <div class="row">
    <div class="col-md-11">
      <h1><a href="/">Storm UI</a></h1>
    </div>
    <div id="ui-user" class="col-md-1"></div>
  </div>
  <div class="row">
      <div class="col-md-12" id="search-form">
      </div>
  </div>
  <div class="row">
    <div class="col-md-12">
      <h2>Topology summary</h2>
      <div id="topology-summary"></div>
    </div>
  </div>
  <div class="row">
      <div class="col-md-12">
          <h2 id="topology-resources-header">Topology resources</h2>
          <div id="topology-resources"></div>
      </div>
  </div>
  <div class="row">
    <div id="topology-actions" class="col-md-12"></div>
  </div>
  <div class="row">
    <div id="topology-stats" class="col-md-12"></div>
  </div>
  <div class="row">
    <div id="topology-spouts-lag" class="col-md-12"></div>
  </div>
  <div class="row">
    <div id="spout-stats" class="col-md-12"></div>
  </div>
  <div class="row">
    <div id="bolt-stats" class="col-md-12"></div>
  </div>
  <div class="row">
    <div id="worker-stats" class="col-md-12"></div>
  </div>
  <div class="row">
    <div id="topology-visualization" class="col-md-12"></div>
  </div>
  <div class="row">
    <div id="topology-configuration" class="col-md-12"></div>
  </div>
  <div class="row">
    <div class="col-md-12">
      <p id="toggle-switch" style="display: block;" class="js-only"></p>
    </div>
  </div>
  <div class="row">
    <div id="json-response-error" class="col-md-12"></div>
  </div>
</div>
</body>
<script>

var toggleChangeLogLevel;

function closeChangeLogLevel (){
    var container = $("#change-log-level");
    container.hide();
}

function clearLoggerLevel(id){
    $("#loggerRemove-" + id).val("true");
    sendLoggerLevel(id);
    $("#logger-" + id).remove();
}

function sendLoggerLevel(id){
    var topologyId = $.url("?id");
    var shouldRemove = $("#loggerRemove-" + id).val() === "true";
    var level = $("#loggerLevel-" + id).val();
    var timeout = parseInt($("#loggerTimeout-" + id).val());
        timeout = isNaN(timeout) ? 0 : timeout;
    var loggerName = $("#loggerName-" + id).val();
    if (level === 'Pick Level'){
        alert ('Please pick a valid log level');
        return;
    }
    var levelBelowInfo = level === 'DEBUG';

    if (!shouldRemove && levelBelowInfo && timeout <= 0){
        timeout = parseInt (
            prompt ("You must provide a timeout > 0 for DEBUG log level. What timeout would you like (secs)?", 30));
        if (!timeout){
            return;
        }
    }
    var data = {};
    var loggerSetting;

    if (id === 0) {
        data["namedLoggerLevels"] = {};
        data["namedLoggerLevels"]["ROOT"] = {};
        loggerSetting = data["namedLoggerLevels"]["ROOT"];
    } else {
        data["namedLoggerLevels"] = {};
        data["namedLoggerLevels"][loggerName] = {};
        loggerSetting = data["namedLoggerLevels"][loggerName];
    }

    loggerSetting.target_level = shouldRemove ? null : level;
    loggerSetting.reset_level  = "INFO";
    loggerSetting.timeout = timeout;

    sendRequest (topologyId, "logconfig", null, data, toggleChangeLogLevel);
};

function renderLogLevelForm (template, responseData){
    var topologyId = $.url("?id");
    var container = $("#change-log-level");

    var levels = [
        {name: "Pick Level"},
        {name: "ALL"},
        {name: "TRACE"},
        {name: "DEBUG"},
        {name: "INFO" },
        {name: "WARN" },
        {name: "ERROR"},
        {name: "FATAL"},
        {name: "OFF"}
    ];
    var partialTemplates = $(template).filter('.partials');
    var partials = {};

    $.each(partialTemplates, function (ix, partial){
        var obj = $(partial);
        partials [obj.attr('id')] = obj.html(); 
    });

    var logLevelTemplate = $(template).filter("#topology-change-log-level-template").html();

    var calcAbsoluteTimeout = function (timeout_epoch) {
        var absoluteTimeout = "";
        if (timeout_epoch) {
            var d = new Date(0); 
            d.setUTCSeconds(timeout_epoch / 1000);
            absoluteTimeout = d.toLocaleDateString() + " " + d.toLocaleTimeString();
        }
        return absoluteTimeout;
    };
    var renderImpl = function (data){
        var loggers = [];
        var loggerCount = 1;
        var obj = data.namedLoggerLevels;
        if (!obj) {
            obj = {};
            data.namedLoggerLevels = obj;
        }

        var sortedLoggers = Object.keys(obj).sort(function (l1, l2){
            if (l1 === "ROOT") return -1;
            if (l2 === "ROOT") return 1;
            return l1 > l2;
        });

        sortedLoggers.forEach (function (l){
            var obj = data.namedLoggerLevels[l];
            obj.loggerId = loggerCount++;
            obj.loggerName = l;
            obj.named = l != "ROOT";
            obj.cls = "namedLoggers";
            obj.levelSelected = function (obj){
                return function (){
                    return this.name === obj.target_level ? "selected" : "";
                }
            }(obj);
            obj.absoluteTimeout = calcAbsoluteTimeout (obj.timeout_epoch);
            obj.canClear = true;
            loggers.push(obj);
        });

        loggers.push({
            loggerId: loggerCount,
            isNew: true,
            cls: 'newLogger'
        });

        var tmplData = {
            loggers: loggers,
            levels: levels
        };

        container.html(Mustache.render(logLevelTemplate, tmplData, partials));
        container.show('fast');
    };
    if (!responseData) {
        var topologyId = $.url("?id");
        $.get('/api/v1/topology/' + topologyId + '/logconfig', renderImpl);
    } else {
        renderImpl (responseData);
    }
} 
$(document).ajaxStop($.unblockUI);
$(document).ajaxStart(function(){
    if ($("#topology-visualization").children().size() == 0) {
        $.blockUI({ message: '<img src="images/spinner.gif" /> <h3>Loading topology summary...</h3>'});
    }
});
$(document).ready(function() {
    var topologyId = $.url("?id");
    var tableStateKey = ":".concat(topologyId);
    var window = $.url("?window");
    var sys = $.cookies.get("sys") || "false";
    var url = "/api/v1/topology/"+topologyId+"?sys="+sys;
    if(window) url += "&window="+window;
    $.extend( $.fn.dataTable.defaults, {
      stateSave: true,
      stateSaveCallback: function (oSettings, oData) {
        sessionStorage.setItem( oSettings.sTableId.concat(tableStateKey), JSON.stringify(oData) );
      },
      stateLoadCallback: function (oSettings) {
        return JSON.parse( sessionStorage.getItem(oSettings.sTableId.concat(tableStateKey)) );
      },
      lengthMenu: [[20,40,60,100,-1], [20, 40, 60, 100, "All"]],
      pageLength: 20
    });

    renderToggleSys($("#toggle-switch"));
    $.ajaxSetup({
        "error":function(jqXHR,textStatus,response) {
            var errorJson = jQuery.parseJSON(jqXHR.responseText);
            getStatic("/templates/json-error-template.html", function(template) {
                $("#json-response-error").append(Mustache.render($(template).filter("#json-error-template").html(),errorJson));
            });
        }
    });

    $.getJSON(url,function(response,status,jqXHR) {
        var uiUser = $("#ui-user");
        getStatic("/templates/user-template.html", function(template) {
            uiUser.append(Mustache.render($(template).filter("#user-template").html(),response));
            $('#ui-user [data-toggle="tooltip"]').tooltip();
        });

        var topologySummary = $("#topology-summary");
        var topologyResources = $("#topology-resources");
        var topologyStats = $("#topology-stats");
        var topologySpoutsLag = $("#topology-spouts-lag");
        var spoutStats = $("#spout-stats");
        var boltStats = $("#bolt-stats");
        var workerStats = $("#worker-stats");
        var config = $("#topology-configuration");
        var topologyActions = $("#topology-actions");
        var topologyVisualization = $("#topology-visualization")
        var searchForm = $("#search-form")
        var formattedConfig = formatConfigData(response["configuration"]);
        var buttonJsonData = topologyActionJson(response["id"],response["encodedId"],response["name"],response["status"]
                                                ,response["msgTimeout"],response["debug"],response["samplingPct"]);
        $.ajax ({url: "/templates/topology-page-template.html", success: function(template) {
            toggleChangeLogLevel = function (data) {
              renderLogLevelForm (template, data);
            }
            searchForm.append(Mustache.render($(template).filter("#search-form-template").html(),{id: topologyId}));
            topologySummary.append(Mustache.render($(template).filter("#topology-summary-template").html(),response));
            topologyResources.append(Mustache.render($(template).filter("#topology-resources-template").html(),response));
            var displayResource = response["schedulerDisplayResource"];
            if (!displayResource){
                $('#topology-resources-header').hide();
                $('#topology-resources').hide();
            }
            topologyActions.append(Mustache.render($(template).filter("#topology-actions-template").html(),buttonJsonData));
            topologyStats.append(Mustache.render($(template).filter("#topology-stats-template").html(),response));
            //window, emitted, transferred, complete latency, acked, failed
            $("#topology-stats-table").DataTable({
              paging: false,
              info: false,
              searching: false,
              columnDefs: [
                {type: "num", targets: [1, 2, 3, 4, 5]},
                {type: "time-str", targets: [0]}
              ]
            });

            spoutStats.append(Mustache.render($(template).filter("#spout-stats-template").html(),response));
            //id, executors, tasks, emitted, transferred, complete latency, acked, failed, Last error
            dtAutoPage("#spout-stats-table", {
              columnDefs: [
                {type: "num", targets: [1, 2, 3, 4, 5, 6, 7]}
              ]
            });

            boltStats.append(Mustache.render($(template).filter("#bolt-stats-template").html(),response));
            //id, executors, tasks, emitted, transferred, capacity, execute latency, executed, process latency, acked, failed, last error
            dtAutoPage("#bolt-stats-table", {
              columnDefs: [
                {type: "num", targets: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}
              ]
            });

          jsError(function() {
            workerStats.append(Mustache.render($(template).filter("#worker-stats-template").html(),response));
            makeTopologyWorkerStatsTable (response, '#worker-stats-table', '#worker-stats');
          });

          jsError(function() {
            topologyVisualization.append(Mustache.render($(template).filter("#topology-visualization-template").html(), response));
            $("#show-hide-visualization").click(function () { show_visualization(null) });

            config.append(Mustache.render($(template).filter("#topology-configuration-template").html(),formattedConfig));
            $('#topology-configuration td').jsonFormatter()
            //key, value
            dtAutoPage("#topology-configuration-table", {});

            var errorCells = document.getElementsByClassName("errorSpan");
            for (i =0; i < errorCells.length; i++)
            {
              var timeLapsedInSecs = errorCells[i].id;
              if (parseInt(timeLapsedInSecs) < 1800) {
                errorCells[i].style.color = "#9d261d";
                errorCells[i].style.borderBottomColor = "#9d261d";
              }
            }
            
            var errorTime = document.getElementsByClassName("errorTime");
            for (i=0; i < errorTime.length; i++)
            {
              if((errorTime[i].id))
              {
                var a = new Date(parseInt(errorTime[i].id) * 1000);
                var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
                var days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thur', 'Fri', 'Sat'];
                var year = a.getFullYear();
                var month = months[a.getMonth()];
                var date = a.getDate();
                var hour = a.getHours();
                var min = a.getMinutes();
                var sec = a.getSeconds();
                var day = days[a.getDay()];
                if (hour < 10) {hour = "0"+hour;}
                if (min  < 10) {min  = "0"+min;}
                if (sec  < 10) {sec  = "0"+sec;}
                var time = day + ', '+date + ' ' + month + ' ' + year + ' ' + hour + ':' + min + ':' + sec;
                
                errorTime[i].innerHTML = time;
                var sec_num = parseInt(errorTime[i].title, 10);
                var hours   = Math.floor(sec_num / 3600);
                var minutes = Math.floor((sec_num - (hours * 3600)) / 60);
                var seconds = sec_num - (hours * 3600) - (minutes * 60);
                if (hours   < 10) {hours   = "0"+hours;}
                if (minutes < 10) {minutes = "0"+minutes;}
                if (seconds < 10) {seconds = "0"+seconds;}
                var time    = hours+':'+minutes+':'+seconds;
                errorTime[i].title = "Elapsed Time Since Error: " + time;
              }
            }
            $('#topology-summary [data-toggle="tooltip"]').tooltip();
            $('#topology-stats [data-toggle="tooltip"]').tooltip();
            $('#spout-stats [data-toggle="tooltip"]').tooltip();
            $('#bolt-stats [data-toggle="tooltip"]').tooltip();
            $('#topology-configuration [data-toggle="tooltip"]').tooltip();
            $('#topology-actions [data-toggle="tooltip"]').tooltip();
            $('#topology-visualization [data-toggle="tooltip"]').tooltip();

            var lagUrl = "/api/v1/topology/"+topologyId+"/lag";
            $.getJSON(lagUrl,function(lagResponse,status,jqXHR) {
                if (lagResponse !== null && lagResponse !== undefined) {
                    var kafkaSpoutsLagTemplate = $(template).filter("#topology-kafka-spouts-lag-template").html();
                    var spoutsErrorTemplate = $(template).filter("#topology-spouts-lag-error-template").html();

                    var data = {};
                    data.kafkaSpoutsLagResults = [];
                    data.spoutsLagErrorResults = [];
                    for (var spoutId in lagResponse) {
                        var spout = lagResponse[spoutId];
                        var spoutType = spout.spoutType;
                        if (spoutType !== "KAFKA") {
                            continue;
                        }
                        var spoutLagResult = spout.spoutLagResult;
                        var errorInfo = spout.errorInfo;
                        if (spoutLagResult !== undefined) {
                            for (var topicName in spoutLagResult) {
                                var topicLagResult = spoutLagResult[topicName];
                                for (var partitionId in topicLagResult) {
                                    var partitionLagResult = topicLagResult[partitionId];
                                    data.kafkaSpoutsLagResults.push({
                                        id: spoutId,
                                        topic: topicName,
                                        partition: partitionId,
                                        logHeadOffset: partitionLagResult.logHeadOffset,
                                        consumerCommittedOffset: partitionLagResult.consumerCommittedOffset,
                                        lag: partitionLagResult.lag
                                    });
                                }
                            }
                        } else if (errorInfo !== undefined) {
                            data.spoutsLagErrorResults.push({
                                spoutId: spoutId,
                                spoutType: spout.spoutType,
                                errorInfo: errorInfo
                            });
                        }
                    }

                    if (data.kafkaSpoutsLagResults.length > 0) {
                        topologySpoutsLag.append(Mustache.render(kafkaSpoutsLagTemplate, data));
                    }
                    if (data.spoutsLagErrorResults.length > 0) {
                        topologySpoutsLag.append(Mustache.render(spoutsErrorTemplate, data));
                    }
                }
            });
          });
      }});
    });
 });
</script>
</html>

